The code presented here is implemented in Turbo Pascal For Windows. The concepts illustrated apply to any Windows development environment. This code is adapted from code I wrote for TurboPower Software's NETBIOS unit included with B-Tree Filer network version. It is reproduced here with TurboPower's permission. The code is provided in five source files. They are each described below: WINDPMI.PAS This is a unit that implements the the core DPMI routines. This unit is a trimed down version of the unit by the same name distributed by TurboPower Software. TNETBIOS.PAS This unit defines the various NetBIOS constants and types used by the other source files. WNETBIOS This file implements the NetBIOS calls in a DLL. UNETBIOS This unit defines the interface for the routine in WNETBIOS.DLL. NBCHAT A simple NetBIOS chat program that runs under Windows. NBCHAT is a primitive Windows application in that it uses Borland's WinCrt unit. WinCrt is a unit that makes normal Turbo Pascal WriteLn and ReadLn statements work within a Windows application. Using WinCrt greatly reduces the size of the NBCHAT source, an important consideration in a magazine article! The WinDPMI.PAS file contains the types and routines needed to access DPMI services. It also interfaces a useful routine called InRealMode. All of the code presented in this article assumes Windows is operating in protected mode, so if InRealMode returns true our demo program simply displays a message and halts. The source to the WinDPMI unit is written using Turbo Pascal's Inline Assembler. The code looks deceivingly simple. Making the DPMI calls is the easy part, figuring out what to do with them is where the challenge lies! The formal DPMI Specification is available free of charge from Intel. It explains each of the DPMI services provided here in detail. If you plan on doing any serious work using DPMI, I'd highly recommend getting the API specification from Intel. The next source file, TNETBIOS.PAS, is straitforward. It defines the NetBIOS Network Control Block structure. Probably the most interesting record is the WindowsPostType. When writing this code, the part that gave me the most trouble was attempting to figure out how to deal with NetBIOS Post Routines. The WindowsPostType is a key component of my solution (more on this later). WNETBIOS.PAS implements the meat of the routines. It is here that DPMI services are used to access the NetBIOS API. The first two routines in the source are routines that act as a shell around the Windows GlobalDosAlloc and GlobalDosFree routines. The TNETBIOS unit defines a WinNCB as follows: WinNCB = record NR, NP : NCBPtr; end; WinNCB is a record holding two pointers to NCBs. NR is a pointer is real mode format (segment:offset), and NP is a pointer in protected mode format (selector:offset). Keep in mind, these two pointers both point to the same block of memory. AllocateWinNCB allocates the real mode memory and sets up these two pointers, and FreeWinNCB returns the real mode memory to Windows. Next come two low level routines for making the NetBIOS calls, ClearNCB and NetBIOSRequest. NetBIOSRequest makes the call to the NetBIOS API by initializing the important fields of a DPMIRegisters variable, then calling the DPMI Simulate Real Mode Interrupt service. Note that the DPMIRegisters variable is filled with zeros before the other fields are initialized. This is important, since invalid or uninitialized values in segment registers or SS:SP will cause problems for DPMI. The NetBIOSRequest procedure takes a single parameter of type NCBPtr. It is the real mode pointer that is passed to this routine. Care is taken in the body of NetBIOSRequest to ensure that Turbo Pascal doesn't generate code to load that pointer into a register pair. Why, you ask? Because if Turbo Pascal generated its usual code: LES DI, SomePtr the CPU would generate an exception error since you would be attempting to load an invalid selector into the ES segment register! Whenever a segment register is loaded in protected mode, the CPU will check to see if it is a valid selector, and if not, a protection fault is triggered. The typecasts are used to cause Turbo Pascal to treat the segment and offset components of the pointer as words, not pointers, alleviating the concern that an LES DI statement may be used. The next 5 routines make various NetBIOS API calls. They construct an NCB (accessing the NP protected mode pointer of the WinNCB type), then call NetBIOSRequest (passing the NR real mode pointer of the WinNCB type). Notice how the NCB field PostRoutine is initialized in SendDatagram and ReceiveDatagram. It is passed the field Callback from the WindowsPostType. This is the value returned by the DPMI service Allocate Real Mode Callback. More on this when we reach this file's most intriquing routine, CallbackShell. The routine CancelRequest, NetBIOSAddName, and NetBIOSDeleteName all allocate, use, and dispose of their own internal WinNCB variables. They can do this because the routines in question don't return until NetBIOS has completed the request. Therefore there is no need for the caller to assure the NCBs remain static, so to simplify things the NCBs are dealt with internally. Note that CancelRequest also takes a WinNCB as a parameter. That parameter is the WinNCB for the NetBIOS command to be cancelled. The CancelRequest routine still needs its own internal WinNCB to issue the cancel command itself. The next three routines deal with determining NetBIOS' presence. We resort to using another DPMI routine to determine the current value of the real mode interrupt 5Ch vector. If the vector is NIL or pointing to the BIOS, then we know NetBIOS isn't installed. Otherwise, we issue an invalid NetBIOS command and see if the interrupt 5Ch handler installed returns the NetBIOS invalid command error. If so, NetBIOS is installed, if not, something else is processing interrupt 5Ch and NetBIOS is not present. It is interesting that IBM forgot to include a NetBIOS Present function call when they designed NetBIOS! Now comes the real hairy part. It is no trivial matter to deal with NetBIOS Post Routines! The last three routines in WNETBIOS deal with this tricky issue. Before we get to the code, lets examine the data structures involved more closely: RealModeCallbackProc = Pointer; NetBiosPostRoutine = procedure(LastError : Byte; N : WinNCBPtr); WindowsPostType = record Regs : DPMIRegisters; Post : NetBiosPostRoutine; CallBack : RealModeCallbackProc; DataSegm : Word; NCBs : WinNCBPtr; end; The DPMI AllocateRealModeCallbackAddr procedure requires a pointer to a protected mode procedure to call, a variable of type DPMIRegisters to pass data in, and a pointer to return the real mode callback address. In our WindowsPostType, Regs is the DPMIRegisters, and Callback is the real mode callback address returned by DPMI. Post is a routine of type NetBIOSPostRoutine which will ultimately be called, and NCBs is a pointer to the WinNCB associated with this post routine. The DataSegm field is the data segment of the program using the callback. The WindowsPostType contains all the data necessary to pull off the tough task of allowing our protected mode application to take advantage of NetBIOS Post Routines. WNETBIOS defines a routine called GetWindowsPostRoutine to fill in the WindowsPostType variable. GetWindowsPostRoutine is passed the NetBIOSPostRoutine to be called, the data segment of the caller, and a variable of type WindowsPostType. This routine calls the DPMI Allocate Real Mode Callback Address service to obtain a pointer to a procedure that can be called in real mode. This real mode callback will in turn call a specified protected mode procedure. If you look at the source to GetWindowsPostRoutine, you'll see the protected mode procedure we ask the real mode callback to call is not the NetBIOSPostRoutine, but rather a routine called CallbackShell. CallbackShell is where we separate the men from the boys. This is a routine that knows the context of the system at the time the real mode callback calls the protected mode routine. CallbackShell then gets the information it needs to safely call the NetBIOSPostRoutine that had been specified in the call to GetWindowsPostRoutine. DPMI doesn't go out of its way to make this easy for you. When the protected mode routine is called by the callback, you must grab the return address off of the real mode stack, place it into the drCS and drIP fields of the DPMIRegisters variable, and issue an IRET instruction. On entry into the protected mode routine, DPMI's real mode callback places a pointer to the real mode stack in DS:SI (DS contains a selector), and a pointer to the DPMIRegisters type in ES:DI. Our CallbackShell routine has the task of calling the NetBIOSPostRoutine. CallbackShell takes the following action: 1) save all the registers we'll be modifying 2) grab the return address off the real mode stack 3) put the return address in the drCS:drIP fields of the DPMIRegisters 4) push the NetBIOS return code 5) push a pointer to the WinNCB associated with this event 6) setup the client program's DATA segment 7) call the user specified NetBIOSPostRoutine 8) restore the registers 9) issue an IRET The need for callback routines is not limited to NetBIOS. Similar steps must be taken for Event Service Routines under NetWare's IPX and SPX protocols. The UNETBIOS.PAS unit defines the interface to the WNETBIOS DLL. The final source file is the NBCHAT.PAS program. This is a quick and dirty chat program designed to test the routines presented here. The NBCHAT program first confirms that Windows is operating in Standard or 386 Enhanced mode. It then checks to see if NetBIOS is loaded. If so, it prompts the user for a NetBIOS name to give this workstation, and a name for the chat partner. It then adds the NetBIOS name for this workstation, allocates necessary resources, and enters the message loop. The program sets up an ExitProc to handle program termination. Global typed constants (in Turbo Pascal, typed constants are simply initialized variables) are defined to track the state of various variables. The ExitProc uses these booleans to safely shut down any pending NetBIOS events and to release all allocated resources. The main program logic is implemented in two routines: MessageLoop and PostRoutine. Here's how MessageLoop is implemented: procedure MessageLoop; var C : Char; begin WriteLn('Press space bar to enter message, ESC to quit'); ReceiveDatagram(RecN, NBNameNo, False, Post, SizeOf(String), SR); repeat while not KeyPressed do begin if Pending then ShowIncoming; end; C := ReadKey; if C <> ^[ then SendOutgoing; until C = ^[; end; MessageLoop posts the first ReceiveDatagram call. It then sits in a loop until the escape key is pressed. If a key other than escape is pressed, then the user is prompted for a message to send, and the message is sent to the chat partner. While not processing messages to send, the loop constantly checks the status of the global boolean Pending. When Pending becomes True, it means an incoming message is waiting to be processed. When the first ReceiveDatagram call is made by MessageLoop, it associates the call with a NetBIOS Post Routine appropriately called PostRoutine. When a datagram is received, NetBIOS (and our DPMI callback logic) will automatically call the Post Routine. Our Post Routine looks like this: procedure PostRoutine(LastError : Byte; N : WinNCBPtr); Far; begin if Exiting then Exit; Pending := True; if LastError = 0 then Msg := SP^ else Msg := 'NetBIOS error = ' + Num2Str(LastError); ReceiveDatagram(N^, NBNameNo, False, Post, SizeOf(String), SR); end; It first checks to see if the program is shutting down in the ExitProc. If so, it exits without doing anything else. This prevents the PostRoutine from reissuing the ReceiveDatagram call. If the program isn't exiting, the global boolean Pending is set to True. If NetBIOS encountered an error, the global variable Msg is set to an error message, otherwise it is given the incoming string. Then the ReceiveDatagram call is reissued so NetBIOS can receive the next incoming datagram. Note that a Post Routine executes in an interrupts off state, much as if it was a interrupt service routine. You must be very careful what you do in post routines. This post routine simply sets some variables indicating a message has arrived and resubmits the WinNCB to listen for the next datagram. You cannot call DOS services from a Post Routine, but you may call most NetBIOS API calls. The global variables SR and SP are pointers to strings. SP is a protected mode pointer, and SR is a real mode pointer. They are allocated through a call to GlobalDosAlloc. When accessing the string in our application, we must use SP. However, when we pass the string to NetBIOS, we must use SR, the real pointer. This is the same issue as discussed earlier when talking about the WinNCB record. This is an overly simplistic program. In the real world, a program would probably post multiple ReceiveDatagram calls using a pool of WinNCBs to make sure rapidly incoming events were processed properly. The goal here wasn't to write the ultimate NetBIOS chat program, but to show how to use real mode API and callback services within a protected mode application.